library(tidyverse)
── Attaching core tidyverse packages ──────────────────────────────── tidyverse 2.0.0 ──
✔ dplyr     1.1.4     ✔ readr     2.1.5
✔ forcats   1.0.0     ✔ stringr   1.5.1
✔ ggplot2   3.5.1     ✔ tibble    3.2.1
✔ lubridate 1.9.4     ✔ tidyr     1.3.1
✔ purrr     1.0.4     ── Conflicts ────────────────────────────────────────────────── tidyverse_conflicts() ──
✖ dplyr::filter() masks stats::filter()
✖ dplyr::lag()    masks stats::lag()
ℹ Use the ]8;;http://conflicted.r-lib.org/conflicted package]8;; to force all conflicts to become errors
library(WGCNA)
Loading required package: dynamicTreeCut
Loading required package: fastcluster

Attaching package: ‘fastcluster’

The following object is masked from ‘package:stats’:

    hclust


Registered S3 methods overwritten by 'htmltools':
  method               from         
  print.html           tools:rstudio
  print.shiny.tag      tools:rstudio
  print.shiny.tag.list tools:rstudio
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio
Registered S3 method overwritten by 'rmarkdown':
  method         from
  print.paged_df     
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

Attaching package: ‘WGCNA’

The following object is masked from ‘package:stats’:

    cor
library(gplots)

Attaching package: ‘gplots’

The following object is masked from ‘package:stats’:

    lowess
options(stringsAsFactors = FALSE)

load sample info

sample.description <- read.csv("../output/sample.description.gametophyte.all.csv")

load reads

lcpm <- read.csv("../output/gam_combined_log2cpm.csv.gz", row.names = 1, check.names = FALSE)
head(lcpm)
dim(lcpm)
[1] 27206    71

Filter for genes with the highest coefficient of variation

CV <- apply(lcpm, 1, \(x) abs(sd(x)/mean(x)))
hist(log10(CV))

names(CV) <- rownames(lcpm)
CV[str_detect(names(CV), "18G076300|33G031700")]
Ceric.18G076300.v2.1 Ceric.33G031700.v2.1 
           0.1511936            0.6073808 
quantile(CV, 0.40)
      40% 
0.1498988 

LFY2 has a pretty low CV; have to include 60% of the genes by CV to include it.

lcpm.filter <- lcpm[CV > quantile(CV, 0.4),]
dim(lcpm.filter)
[1] 16323    71

WGCNA wants genes in columns

lcpm.filter.t <- t(lcpm.filter)

Soft thresholding

powers <- c(c(1:10), seq(from = 12, to=20, by=2))
sft <- pickSoftThreshold(lcpm.filter.t, powerVector = powers, verbose = 5,networkType = "signed hybrid", blockSize = 20000)
 pickSoftThreshold: calculating connectivity for given powers...
   ..working on genes 1 through 16323 of 16323
Warning: executing %dopar% sequentially: no parallel backend registered
sizeGrWindow(9, 5)
par(mfrow = c(1,2))
cex1 <- 0.9
# Scale-free topology fit index as a function of the soft-thresholding power
plot(sft$fitIndices[,1], -sign(sft$fitIndices[,3])*sft$fitIndices[,2],
     xlab="Soft Threshold (power)",ylab="Scale Free Topology Model Fit,signed R^2",type="n",
     main = paste("Scale independence"))
text(sft$fitIndices[,1], -sign(sft$fitIndices[,3])*sft$fitIndices[,2],
     labels=powers,cex=cex1,col="red")
# this line corresponds to using an R^2 cut-off of h
abline(h=0.90,col="red")
# Mean connectivity as a function of the soft-thresholding power
plot(sft$fitIndices[,1], sft$fitIndices[,5],
     xlab="Soft Threshold (power)",ylab="Mean Connectivity", type="n",
     main = paste("Mean connectivity"))
text(sft$fitIndices[,1], sft$fitIndices[,5], labels=powers, cex=cex1,col="red")

choose 5

softPower <- 5
adjacency <- adjacency(lcpm.filter.t, power = softPower, type = "signed hybrid")
# Turn adjacency into topological overlap
TOM <- TOMsimilarity(adjacency, TOMType = "signed");
..connectivity..
..matrix multiplication (system BLAS)..
..normalization..
..done.
dissTOM <- 1-TOM
# Call the hierarchical clustering function
geneTree <- hclust(as.dist(dissTOM), method = "average")
# Plot the resulting clustering tree (dendrogram)
sizeGrWindow(12,9)
plot(geneTree, xlab="", sub="", main = "Gene clustering on TOM-based dissimilarity",
     labels = FALSE, hang = 0.04)

define modules

# We like large modules, so we set the minimum module size relatively high:
minModuleSize <- 30;
# Module identification using dynamic tree cut:
dynamicMods <- cutreeDynamic(dendro = geneTree, distM = dissTOM,
                             deepSplit <- 2, pamRespectsDendro = FALSE,
                             minClusterSize = minModuleSize);
 ..done.
table(dynamicMods)
dynamicMods
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   17 
3212 1387 1022  997  986  979  920  784  753  734  721  720  611  609  496  380  280 
  18   19   20   21   22 
 224  187  152  133   36 
# Convert numeric labels into colors
dynamicColors = labels2colors(dynamicMods)
table(dynamicColors)
dynamicColors
       black         blue        brown         cyan    darkgreen      darkred 
         920         1387         1022          609           36          133 
       green  greenyellow       grey60    lightcyan   lightgreen  lightyellow 
         986          721          280          380          224          187 
     magenta midnightblue         pink       purple          red    royalblue 
         753          496          784          734          979          152 
      salmon          tan    turquoise       yellow 
         611          720         3212          997 
# Plot the dendrogram and colors underneath
sizeGrWindow(8,6)
plotDendroAndColors(geneTree, dynamicColors, "Dynamic Tree Cut",
                    dendroLabels = FALSE, hang = 0.03,
                    addGuide = TRUE, guideHang = 0.05,
                    main = "Gene dendrogram and module colors")

merge similar modules

# Calculate eigengenes
MEList <- moduleEigengenes(lcpm.filter.t, colors = dynamicColors)
MEs <- MEList$eigengenes
# Calculate dissimilarity of module eigengenes
MEDiss <- 1-cor(MEs);
# Cluster module eigengenes
METree <- hclust(as.dist(MEDiss), method = "average");
# Plot the result
sizeGrWindow(7, 6)
plot(METree, main = "Clustering of module eigengenes",
     xlab = "", sub = "")

merge with correlation > 0.8

MEDissThres = 0.2
# Plot the cut line into the dendrogram
plot(METree, main = "Clustering of module eigengenes",
     xlab = "", sub = "")
abline(h=MEDissThres, col = "red")

# Call an automatic merging function
merge = mergeCloseModules(lcpm.filter.t, dynamicColors, cutHeight = MEDissThres, verbose = 3)
 mergeCloseModules: Merging modules whose distance is less than 0.2
   multiSetMEs: Calculating module MEs.
     Working on set 1 ...
     moduleEigengenes: Calculating 22 module eigengenes in given set.
   multiSetMEs: Calculating module MEs.
     Working on set 1 ...
     moduleEigengenes: Calculating 19 module eigengenes in given set.
   Calculating new MEs...
   multiSetMEs: Calculating module MEs.
     Working on set 1 ...
     moduleEigengenes: Calculating 19 module eigengenes in given set.
# The merged module colors
mergedColors = merge$colors
# Eigengenes of the new merged modules:
mergedMEs = merge$newMEs

compare pre and post merge

sizeGrWindow(12, 9)
#pdf(file = "Plots/geneDendro-3.pdf", wi = 9, he = 6)
plotDendroAndColors(geneTree, cbind(dynamicColors, mergedColors),
                    c("Dynamic Tree Cut", "Merged dynamic"),
                    dendroLabels = FALSE, hang = 0.03,
                    addGuide = TRUE, guideHang = 0.05)

#dev.off()
# Rename to moduleColors
moduleColors = mergedColors
# Construct numerical labels corresponding to the colors
colorOrder = c("grey", standardColors(50));
moduleLabels = match(moduleColors, colorOrder)-1;
MEs = mergedMEs 
table(merge$colors)

       black         blue        brown    darkgreen      darkred        green 
         920         1996         1022           36          133          986 
 greenyellow       grey60    lightcyan   lightgreen  lightyellow      magenta 
         721          280          380          224         1166         1473 
midnightblue         pink       purple    royalblue       salmon    turquoise 
         496          784          734          152          611         3212 
      yellow 
         997 
length(table(merge$colors))
[1] 19
median(table(merge$colors))
[1] 734

Look at modules

Which module is LFY in?

CrLFY1 <- "Ceric.33G031700" 

CrLFY2 <- "Ceric.18G076300"
module.assignment <- tibble(geneID=colnames(lcpm.filter.t), module = mergedColors)

module.assignment %>%
  filter(str_detect(geneID, "18G076300|33G031700"))

Interesting: they are in different modules(!).

module.assignment %>% group_by(module) %>% summarize(n_genes = n()) %>% arrange(n_genes)

Plot eigengenes

Make sure sample info sheet is in the correct order.

rownames(lcpm.filter.t) %>% str_replace_all("\\.", "-") == sample.description$sample
 [1] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[17] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[33] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[49] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
[65] TRUE TRUE TRUE TRUE TRUE TRUE TRUE
sample.eigen <- cbind(sample.description, MEs)
sample.eigen
sample.eigen.mean <- sample.eigen %>%
  group_by(group) %>%
  summarize(across(starts_with("ME"), mean),
            label=unique(label))
sample.eigen %>%
  pivot_longer(starts_with("ME"), names_to = "ME") %>%
  ggplot(aes(y=label, x = value)) +
  geom_point() +
  facet_wrap(~ME, ncol=5) +
  theme(axis.text.y = element_text(size = 6))

A heat map:

sample.eigen.mean$group %>% sort
 [1] "CrLFY1-OX-gam"                                 
 [2] "CrLFY2-OX-gam"                                 
 [3] "Cross-gam"                                     
 [4] "PRJEB33372-mature gametophyte"                 
 [5] "PRJNA1121398-5 DAG CrHAM KF hermaphrodite"     
 [6] "PRJNA1121398-5 DAG CrHAM KF male"              
 [7] "PRJNA1121398-5 DAG WT hermaphrodite"           
 [8] "PRJNA1121398-5 DAG WT male"                    
 [9] "PRJNA1121398-8 DAG CrHAM KF hermaphrodite"     
[10] "PRJNA1121398-8 DAG WT hermaphrodite"           
[11] "PRJNA1149654-whole gametophyte DMSO"           
[12] "PRJNA1149654-whole gametophyte IAA"            
[13] "PRJNA248887-4.5 DAG gametophyte"               
[14] "PRJNA248887-4.5 DAG gametophyte + antheridogen"
[15] "PRJNA651769-mature gametophyte"                
[16] "PRJNA651770-immature gametophyte"              
[17] "PRJNA681601-Cr_Herm"                           
[18] "PRJNA681601-Cr_Male"                           
[19] "RNAi-gam"                                      
[20] "WT-male_gam"                                   
[21] "WT-mature_herm"                                
sample.eigen.mean$label %>% sort
 [1] "4.5 DAG gametophyte"                "4.5 DAG gametophyte + antheridogen"
 [3] "5 DAG CrHAM KF hermaphrodite"       "5 DAG CrHAM KF male"               
 [5] "5 DAG WT hermaphrodite"             "5 DAG WT male"                     
 [7] "8 DAG CrHAM KF hermaphrodite"       "8 DAG WT hermaphrodite"            
 [9] "Cr_Herm"                            "Cr_Male"                           
[11] "CrLFY1-OX-gam"                      "CrLFY2-OX-gam"                     
[13] "Cross-gam"                          "immature gametophyte"              
[15] "mature gametophyte"                 "mature gametophyte"                
[17] "RNAi-gam"                           "whole gametophyte DMSO"            
[19] "whole gametophyte IAA"              "WT-male_gam"                       
[21] "WT-mature_herm"                    
MEs.m <- sample.eigen.mean %>% mutate(label=make.names(label, unique = TRUE)) %>% column_to_rownames("label") %>% select(starts_with("ME")) %>% as.matrix()
heatmap.2(MEs.m, trace="none", cexRow= 0.6, col="bluered")

save(module.assignment, MEs, lcpm.filter, CrLFY1, CrLFY2, file="../output/WGCNA_gametophyte_all.Rdata")
LS0tCnRpdGxlOiAiMDRfV0dDTkEiCmF1dGhvcjogIkp1bGluIE1hbG9vZiIKZGF0ZTogIjIwMjUtMDItMTYiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KFdHQ05BKQpsaWJyYXJ5KGdwbG90cykKb3B0aW9ucyhzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYAoKbG9hZCBzYW1wbGUgaW5mbwpgYGB7cn0Kc2FtcGxlLmRlc2NyaXB0aW9uIDwtIHJlYWQuY3N2KCIuLi9vdXRwdXQvc2FtcGxlLmRlc2NyaXB0aW9uLmdhbWV0b3BoeXRlLmFsbC5jc3YiKQpgYGAKCgpsb2FkIHJlYWRzCgpgYGB7cn0KbGNwbSA8LSByZWFkLmNzdigiLi4vb3V0cHV0L2dhbV9jb21iaW5lZF9sb2cyY3BtLmNzdi5neiIsIHJvdy5uYW1lcyA9IDEsIGNoZWNrLm5hbWVzID0gRkFMU0UpCmhlYWQobGNwbSkKZGltKGxjcG0pCmBgYAoKRmlsdGVyIGZvciBnZW5lcyB3aXRoIHRoZSBoaWdoZXN0IGNvZWZmaWNpZW50IG9mIHZhcmlhdGlvbgoKYGBge3J9CkNWIDwtIGFwcGx5KGxjcG0sIDEsIFwoeCkgYWJzKHNkKHgpL21lYW4oeCkpKQpoaXN0KGxvZzEwKENWKSkKYGBgCgpgYGB7cn0KbmFtZXMoQ1YpIDwtIHJvd25hbWVzKGxjcG0pCkNWW3N0cl9kZXRlY3QobmFtZXMoQ1YpLCAiMThHMDc2MzAwfDMzRzAzMTcwMCIpXQpgYGAKCmBgYHtyfQpxdWFudGlsZShDViwgMC40MCkKYGBgCgpMRlkyIGhhcyBhIHByZXR0eSBsb3cgQ1Y7IGhhdmUgdG8gaW5jbHVkZSA2MCUgb2YgdGhlIGdlbmVzIGJ5IENWIHRvIGluY2x1ZGUgaXQuCmBgYHtyfQpsY3BtLmZpbHRlciA8LSBsY3BtW0NWID4gcXVhbnRpbGUoQ1YsIDAuNCksXQpkaW0obGNwbS5maWx0ZXIpCmBgYAoKCgoKV0dDTkEgd2FudHMgZ2VuZXMgaW4gY29sdW1ucwoKYGBge3J9CmxjcG0uZmlsdGVyLnQgPC0gdChsY3BtLmZpbHRlcikKYGBgCgoKU29mdCB0aHJlc2hvbGRpbmcKYGBge3J9CnBvd2VycyA8LSBjKGMoMToxMCksIHNlcShmcm9tID0gMTIsIHRvPTIwLCBieT0yKSkKc2Z0IDwtIHBpY2tTb2Z0VGhyZXNob2xkKGxjcG0uZmlsdGVyLnQsIHBvd2VyVmVjdG9yID0gcG93ZXJzLCB2ZXJib3NlID0gNSxuZXR3b3JrVHlwZSA9ICJzaWduZWQgaHlicmlkIiwgYmxvY2tTaXplID0gMjAwMDApCmBgYAoKYGBge3J9CnNpemVHcldpbmRvdyg5LCA1KQpwYXIobWZyb3cgPSBjKDEsMikpCmNleDEgPC0gMC45CiMgU2NhbGUtZnJlZSB0b3BvbG9neSBmaXQgaW5kZXggYXMgYSBmdW5jdGlvbiBvZiB0aGUgc29mdC10aHJlc2hvbGRpbmcgcG93ZXIKcGxvdChzZnQkZml0SW5kaWNlc1ssMV0sIC1zaWduKHNmdCRmaXRJbmRpY2VzWywzXSkqc2Z0JGZpdEluZGljZXNbLDJdLAogICAgIHhsYWI9IlNvZnQgVGhyZXNob2xkIChwb3dlcikiLHlsYWI9IlNjYWxlIEZyZWUgVG9wb2xvZ3kgTW9kZWwgRml0LHNpZ25lZCBSXjIiLHR5cGU9Im4iLAogICAgIG1haW4gPSBwYXN0ZSgiU2NhbGUgaW5kZXBlbmRlbmNlIikpCnRleHQoc2Z0JGZpdEluZGljZXNbLDFdLCAtc2lnbihzZnQkZml0SW5kaWNlc1ssM10pKnNmdCRmaXRJbmRpY2VzWywyXSwKICAgICBsYWJlbHM9cG93ZXJzLGNleD1jZXgxLGNvbD0icmVkIikKIyB0aGlzIGxpbmUgY29ycmVzcG9uZHMgdG8gdXNpbmcgYW4gUl4yIGN1dC1vZmYgb2YgaAphYmxpbmUoaD0wLjkwLGNvbD0icmVkIikKIyBNZWFuIGNvbm5lY3Rpdml0eSBhcyBhIGZ1bmN0aW9uIG9mIHRoZSBzb2Z0LXRocmVzaG9sZGluZyBwb3dlcgpwbG90KHNmdCRmaXRJbmRpY2VzWywxXSwgc2Z0JGZpdEluZGljZXNbLDVdLAogICAgIHhsYWI9IlNvZnQgVGhyZXNob2xkIChwb3dlcikiLHlsYWI9Ik1lYW4gQ29ubmVjdGl2aXR5IiwgdHlwZT0ibiIsCiAgICAgbWFpbiA9IHBhc3RlKCJNZWFuIGNvbm5lY3Rpdml0eSIpKQp0ZXh0KHNmdCRmaXRJbmRpY2VzWywxXSwgc2Z0JGZpdEluZGljZXNbLDVdLCBsYWJlbHM9cG93ZXJzLCBjZXg9Y2V4MSxjb2w9InJlZCIpCmBgYApjaG9vc2UgNQoKYGBge3J9CnNvZnRQb3dlciA8LSA1CmFkamFjZW5jeSA8LSBhZGphY2VuY3kobGNwbS5maWx0ZXIudCwgcG93ZXIgPSBzb2Z0UG93ZXIsIHR5cGUgPSAic2lnbmVkIGh5YnJpZCIpCiMgVHVybiBhZGphY2VuY3kgaW50byB0b3BvbG9naWNhbCBvdmVybGFwClRPTSA8LSBUT01zaW1pbGFyaXR5KGFkamFjZW5jeSwgVE9NVHlwZSA9ICJzaWduZWQiKTsKZGlzc1RPTSA8LSAxLVRPTQpgYGAKCmBgYHtyfQojIENhbGwgdGhlIGhpZXJhcmNoaWNhbCBjbHVzdGVyaW5nIGZ1bmN0aW9uCmdlbmVUcmVlIDwtIGhjbHVzdChhcy5kaXN0KGRpc3NUT00pLCBtZXRob2QgPSAiYXZlcmFnZSIpCiMgUGxvdCB0aGUgcmVzdWx0aW5nIGNsdXN0ZXJpbmcgdHJlZSAoZGVuZHJvZ3JhbSkKc2l6ZUdyV2luZG93KDEyLDkpCnBsb3QoZ2VuZVRyZWUsIHhsYWI9IiIsIHN1Yj0iIiwgbWFpbiA9ICJHZW5lIGNsdXN0ZXJpbmcgb24gVE9NLWJhc2VkIGRpc3NpbWlsYXJpdHkiLAogICAgIGxhYmVscyA9IEZBTFNFLCBoYW5nID0gMC4wNCkKYGBgCgpkZWZpbmUgbW9kdWxlcwoKYGBge3J9CiMgV2UgbGlrZSBsYXJnZSBtb2R1bGVzLCBzbyB3ZSBzZXQgdGhlIG1pbmltdW0gbW9kdWxlIHNpemUgcmVsYXRpdmVseSBoaWdoOgptaW5Nb2R1bGVTaXplIDwtIDMwOwojIE1vZHVsZSBpZGVudGlmaWNhdGlvbiB1c2luZyBkeW5hbWljIHRyZWUgY3V0OgpkeW5hbWljTW9kcyA8LSBjdXRyZWVEeW5hbWljKGRlbmRybyA9IGdlbmVUcmVlLCBkaXN0TSA9IGRpc3NUT00sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVlcFNwbGl0IDwtIDIsIHBhbVJlc3BlY3RzRGVuZHJvID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWluQ2x1c3RlclNpemUgPSBtaW5Nb2R1bGVTaXplKTsKdGFibGUoZHluYW1pY01vZHMpCmBgYAoKYGBge3J9CiMgQ29udmVydCBudW1lcmljIGxhYmVscyBpbnRvIGNvbG9ycwpkeW5hbWljQ29sb3JzID0gbGFiZWxzMmNvbG9ycyhkeW5hbWljTW9kcykKdGFibGUoZHluYW1pY0NvbG9ycykKIyBQbG90IHRoZSBkZW5kcm9ncmFtIGFuZCBjb2xvcnMgdW5kZXJuZWF0aApzaXplR3JXaW5kb3coOCw2KQpwbG90RGVuZHJvQW5kQ29sb3JzKGdlbmVUcmVlLCBkeW5hbWljQ29sb3JzLCAiRHluYW1pYyBUcmVlIEN1dCIsCiAgICAgICAgICAgICAgICAgICAgZGVuZHJvTGFiZWxzID0gRkFMU0UsIGhhbmcgPSAwLjAzLAogICAgICAgICAgICAgICAgICAgIGFkZEd1aWRlID0gVFJVRSwgZ3VpZGVIYW5nID0gMC4wNSwKICAgICAgICAgICAgICAgICAgICBtYWluID0gIkdlbmUgZGVuZHJvZ3JhbSBhbmQgbW9kdWxlIGNvbG9ycyIpCmBgYAoKbWVyZ2Ugc2ltaWxhciBtb2R1bGVzCgpgYGB7cn0KIyBDYWxjdWxhdGUgZWlnZW5nZW5lcwpNRUxpc3QgPC0gbW9kdWxlRWlnZW5nZW5lcyhsY3BtLmZpbHRlci50LCBjb2xvcnMgPSBkeW5hbWljQ29sb3JzKQpNRXMgPC0gTUVMaXN0JGVpZ2VuZ2VuZXMKIyBDYWxjdWxhdGUgZGlzc2ltaWxhcml0eSBvZiBtb2R1bGUgZWlnZW5nZW5lcwpNRURpc3MgPC0gMS1jb3IoTUVzKTsKIyBDbHVzdGVyIG1vZHVsZSBlaWdlbmdlbmVzCk1FVHJlZSA8LSBoY2x1c3QoYXMuZGlzdChNRURpc3MpLCBtZXRob2QgPSAiYXZlcmFnZSIpOwojIFBsb3QgdGhlIHJlc3VsdApzaXplR3JXaW5kb3coNywgNikKcGxvdChNRVRyZWUsIG1haW4gPSAiQ2x1c3RlcmluZyBvZiBtb2R1bGUgZWlnZW5nZW5lcyIsCiAgICAgeGxhYiA9ICIiLCBzdWIgPSAiIikKYGBgCgptZXJnZSB3aXRoIGNvcnJlbGF0aW9uID4gMC44CmBgYHtyfQpNRURpc3NUaHJlcyA9IDAuMgojIFBsb3QgdGhlIGN1dCBsaW5lIGludG8gdGhlIGRlbmRyb2dyYW0KcGxvdChNRVRyZWUsIG1haW4gPSAiQ2x1c3RlcmluZyBvZiBtb2R1bGUgZWlnZW5nZW5lcyIsCiAgICAgeGxhYiA9ICIiLCBzdWIgPSAiIikKYWJsaW5lKGg9TUVEaXNzVGhyZXMsIGNvbCA9ICJyZWQiKQojIENhbGwgYW4gYXV0b21hdGljIG1lcmdpbmcgZnVuY3Rpb24KbWVyZ2UgPSBtZXJnZUNsb3NlTW9kdWxlcyhsY3BtLmZpbHRlci50LCBkeW5hbWljQ29sb3JzLCBjdXRIZWlnaHQgPSBNRURpc3NUaHJlcywgdmVyYm9zZSA9IDMpCiMgVGhlIG1lcmdlZCBtb2R1bGUgY29sb3JzCm1lcmdlZENvbG9ycyA9IG1lcmdlJGNvbG9ycwojIEVpZ2VuZ2VuZXMgb2YgdGhlIG5ldyBtZXJnZWQgbW9kdWxlczoKbWVyZ2VkTUVzID0gbWVyZ2UkbmV3TUVzCmBgYAoKY29tcGFyZSBwcmUgYW5kIHBvc3QgbWVyZ2UKYGBge3J9CnNpemVHcldpbmRvdygxMiwgOSkKI3BkZihmaWxlID0gIlBsb3RzL2dlbmVEZW5kcm8tMy5wZGYiLCB3aSA9IDksIGhlID0gNikKcGxvdERlbmRyb0FuZENvbG9ycyhnZW5lVHJlZSwgY2JpbmQoZHluYW1pY0NvbG9ycywgbWVyZ2VkQ29sb3JzKSwKICAgICAgICAgICAgICAgICAgICBjKCJEeW5hbWljIFRyZWUgQ3V0IiwgIk1lcmdlZCBkeW5hbWljIiksCiAgICAgICAgICAgICAgICAgICAgZGVuZHJvTGFiZWxzID0gRkFMU0UsIGhhbmcgPSAwLjAzLAogICAgICAgICAgICAgICAgICAgIGFkZEd1aWRlID0gVFJVRSwgZ3VpZGVIYW5nID0gMC4wNSkKI2Rldi5vZmYoKQpgYGAKCmBgYHtyfQojIFJlbmFtZSB0byBtb2R1bGVDb2xvcnMKbW9kdWxlQ29sb3JzID0gbWVyZ2VkQ29sb3JzCiMgQ29uc3RydWN0IG51bWVyaWNhbCBsYWJlbHMgY29ycmVzcG9uZGluZyB0byB0aGUgY29sb3JzCmNvbG9yT3JkZXIgPSBjKCJncmV5Iiwgc3RhbmRhcmRDb2xvcnMoNTApKTsKbW9kdWxlTGFiZWxzID0gbWF0Y2gobW9kdWxlQ29sb3JzLCBjb2xvck9yZGVyKS0xOwpNRXMgPSBtZXJnZWRNRXMgCnRhYmxlKG1lcmdlJGNvbG9ycykKbGVuZ3RoKHRhYmxlKG1lcmdlJGNvbG9ycykpCm1lZGlhbih0YWJsZShtZXJnZSRjb2xvcnMpKQoKYGBgCgojIyBMb29rIGF0IG1vZHVsZXMKCldoaWNoIG1vZHVsZSBpcyBMRlkgaW4/CgpgYGB7cn0KQ3JMRlkxIDwtICJDZXJpYy4zM0cwMzE3MDAiIAoKQ3JMRlkyIDwtICJDZXJpYy4xOEcwNzYzMDAiCmBgYAoKYGBge3J9Cm1vZHVsZS5hc3NpZ25tZW50IDwtIHRpYmJsZShnZW5lSUQ9Y29sbmFtZXMobGNwbS5maWx0ZXIudCksIG1vZHVsZSA9IG1lcmdlZENvbG9ycykKCm1vZHVsZS5hc3NpZ25tZW50ICU+JQogIGZpbHRlcihzdHJfZGV0ZWN0KGdlbmVJRCwgIjE4RzA3NjMwMHwzM0cwMzE3MDAiKSkKYGBgCkludGVyZXN0aW5nOiB0aGV5IGFyZSBpbiBkaWZmZXJlbnQgbW9kdWxlcyghKS4gIAoKYGBge3J9Cm1vZHVsZS5hc3NpZ25tZW50ICU+JSBncm91cF9ieShtb2R1bGUpICU+JSBzdW1tYXJpemUobl9nZW5lcyA9IG4oKSkgJT4lIGFycmFuZ2Uobl9nZW5lcykKYGBgCgpQbG90IGVpZ2VuZ2VuZXMKCk1ha2Ugc3VyZSBzYW1wbGUgaW5mbyBzaGVldCBpcyBpbiB0aGUgY29ycmVjdCBvcmRlci4KYGBge3J9CnJvd25hbWVzKGxjcG0uZmlsdGVyLnQpICU+JSBzdHJfcmVwbGFjZV9hbGwoIlxcLiIsICItIikgPT0gc2FtcGxlLmRlc2NyaXB0aW9uJHNhbXBsZQpgYGAKCmBgYHtyfQpzYW1wbGUuZWlnZW4gPC0gY2JpbmQoc2FtcGxlLmRlc2NyaXB0aW9uLCBNRXMpCnNhbXBsZS5laWdlbgpgYGAKCmBgYHtyfQpzYW1wbGUuZWlnZW4ubWVhbiA8LSBzYW1wbGUuZWlnZW4gJT4lCiAgZ3JvdXBfYnkoZ3JvdXApICU+JQogIHN1bW1hcml6ZShhY3Jvc3Moc3RhcnRzX3dpdGgoIk1FIiksIG1lYW4pLAogICAgICAgICAgICBsYWJlbD11bmlxdWUobGFiZWwpKQpgYGAKCgpgYGB7ciwgZmlnLmhlaWdodD0xMCwgZmlnLndpZHRoPTEwfQpzYW1wbGUuZWlnZW4gJT4lCiAgcGl2b3RfbG9uZ2VyKHN0YXJ0c193aXRoKCJNRSIpLCBuYW1lc190byA9ICJNRSIpICU+JQogIGdncGxvdChhZXMoeT1sYWJlbCwgeCA9IHZhbHVlKSkgKwogIGdlb21fcG9pbnQoKSArCiAgZmFjZXRfd3JhcCh+TUUsIG5jb2w9NSkgKwogIHRoZW1lKGF4aXMudGV4dC55ID0gZWxlbWVudF90ZXh0KHNpemUgPSA2KSkKYGBgCkEgaGVhdCBtYXA6CgpgYGB7cn0Kc2FtcGxlLmVpZ2VuLm1lYW4kZ3JvdXAgJT4lIHNvcnQKc2FtcGxlLmVpZ2VuLm1lYW4kbGFiZWwgJT4lIHNvcnQKCmBgYAoKYGBge3IsIGZpZy5oZWlnaHQ9N30KTUVzLm0gPC0gc2FtcGxlLmVpZ2VuLm1lYW4gJT4lIG11dGF0ZShsYWJlbD1tYWtlLm5hbWVzKGxhYmVsLCB1bmlxdWUgPSBUUlVFKSkgJT4lIGNvbHVtbl90b19yb3duYW1lcygibGFiZWwiKSAlPiUgc2VsZWN0KHN0YXJ0c193aXRoKCJNRSIpKSAlPiUgYXMubWF0cml4KCkKaGVhdG1hcC4yKE1Fcy5tLCB0cmFjZT0ibm9uZSIsIGNleFJvdz0gMC42LCBjb2w9ImJsdWVyZWQiKQpgYGAKCgpgYGB7cn0Kc2F2ZShtb2R1bGUuYXNzaWdubWVudCwgTUVzLCBsY3BtLmZpbHRlciwgQ3JMRlkxLCBDckxGWTIsIGZpbGU9Ii4uL291dHB1dC9XR0NOQV9nYW1ldG9waHl0ZV9hbGwuUmRhdGEiKQpgYGAKCg==